package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Queue;
import java.util.concurrent.locks.ReentrantLock;

public class UDPTunnel extends Observable implements Runnable{
	
	protected DatagramSocket socket;
	
	protected boolean running;
	
	protected Queue<byte[]> packetQueue;
	protected ReentrantLock queueLock = new ReentrantLock();
	
	protected long lastSendTime;

	/**
	 * Creates a new Tunnel listening/sending on/from internalPort and sending to the externalAdress and externalPort.
	 * Will also only accept incoming packets from said external port and external adress
	 * After creating the Tunnel needs to be run the start the listening
	 * @param external the external adress to conenct to
	 * @param externalPort the external port to connect to
	 * @param internalPort the internal port to listen on / send from
	 * @throws SocketException when the internalPort is already in use
	 * @requires externalAdress != null
	 * @requires internalPort is not in use
	 */
	public UDPTunnel(InetAddress externalAdress, int externalPort, int internalPort) throws SocketException{
		socket = new DatagramSocket(internalPort);
		socket.connect(externalAdress, externalPort);
		
		packetQueue = new LinkedList<byte[]>();
	}
	
	/**
	 * Sends a packet of maximum length over the Tunnel to the host speciefied on creation
	 * @param packet the packet to send over the tunnel
	 * @requires packet.length <= Network.Constants.MAX_PACKET_LENGTH
	 */
	public void send(byte[] packet) throws IOException{
		if(packet.length > Constants.MAX_PAYLOAD_LENGTH) throw new IOException("Packet larger than Network.Constants.MAX_PACKET_LENGTH");
				
		DatagramPacket send = new DatagramPacket(packet, packet.length);
		socket.send(send);
	}

	@Override
	public void run() {
		running = true;
		while(running){
			DatagramPacket rec = new DatagramPacket(new byte[Constants.MAX_PACKET_LENGTH], Constants.MAX_PACKET_LENGTH);
			try {
				socket.receive(rec);
				queueLock.lock();
				packetQueue.add(rec.getData());
				queueLock.unlock();
				setChanged();
				notifyObservers();
			} catch (IOException e) {
				stop();
			}			
		}
	}
	
	/**
	 * Stops this Tunnel from receiving packets
	 * (NOTE, does not close the socket, incoming packets will still
	 * be accepted, just not processed)
	 */
	public void stop(){
		running = false;
	}
	
	/**
	 * returns the first packet in the Queue, this packet will after that no longer
	 * be in the queue. Returns null when there is no item in the queue
	 * @return first packet in the queue or null when the queue is empty
	 */
	public byte[] getPacket(){
		byte[] ret;
		queueLock.lock();
		ret = packetQueue.poll();
		queueLock.unlock();
		return ret;
	}
	
}






















